Skip to content

Conversation

fmoletta
Copy link
Contributor

@fmoletta fmoletta commented Oct 8, 2025

Motivation
In order to properly handle reorgs, we should download headers from the latest announced fcu head until me meet our local chain instead of requesting headers from our current head till we reach the fcu head, as our current head may not be part of the canonical chain after a reorg.
The main behaviour changes imposed by this are that
A) We must request all headers before we can begin to execute the blocks
B) We are no longer able to keep all headers in memory (in case of long syncs)

Description

  • Download all headers from sync_head till we reach our local chain as first step of full sync
  • Remove BlockSyncState & FullBlockSyncState
  • [BONUS] Invalidate child block when we ask if its parent is invalid (fork choice + new payload)

Observations & Follow-Up Work
The current implementation downloads all headers first before processing blocks without any sort of checkpoint system to keep progress in case of unexpected failures or shutdowns. When comparing to other implementations this doesn't seem to be much of an issue as header download phase is pretty quick. Having all headers downloaded as a first step can also allow us to download headers non-sequentially (aka divide the chain into batches and fetch them in parallel). Note that this is not as high priority as it would only affect large full syncs, but it may slow down our efforts when using full sync to test vm behaviour. Potential follow up work:

  • Add a way to track which headers we already downloaded if we restart a full sync after a failure (could be something as simple as looking at which header ranges we have on the DB)
  • Speed up header download phase by using parallel requests for different block ranges
  • Evaluate whether SnapBlockSyncState struct can be improved/removed

Closes #4717 + Closes #4776

@github-actions github-actions bot added the L1 Ethereum client label Oct 8, 2025
@fmoletta fmoletta changed the title feat(l1): download all headers from new to old as first step of full sync [Early Draft] feat(l1): download all headers from new to old as first step of full sync Oct 8, 2025
@github-actions github-actions bot removed the L1 Ethereum client label Oct 8, 2025
Copy link

github-actions bot commented Oct 8, 2025

Lines of code report

Total lines added: 104
Total lines removed: 123
Total lines changed: 227

Detailed view
+----------------------------------------------------+-------+------+
| File                                               | Lines | Diff |
+----------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/peer_handler.rs       | 1752  | +2   |
+----------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/sync.rs               | 1355  | -123 |
+----------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/engine/fork_choice.rs | 360   | +4   |
+----------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/engine/payload.rs     | 691   | +4   |
+----------------------------------------------------+-------+------+
| ethrex/crates/storage/api.rs                       | 240   | +7   |
+----------------------------------------------------+-------+------+
| ethrex/crates/storage/store.rs                     | 1518  | +13  |
+----------------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/in_memory.rs        | 626   | +29  |
+----------------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/rocksdb.rs          | 1250  | +43  |
+----------------------------------------------------+-------+------+
| ethrex/tooling/reorgs/src/main.rs                  | 270   | +2   |
+----------------------------------------------------+-------+------+

@fmoletta fmoletta changed the title [Early Draft] feat(l1): download all headers from new to old as first step of full sync feat(l1): download all headers from new to old as first step of full sync Oct 14, 2025
@fmoletta fmoletta force-pushed the full-sync-download-headers-first branch from fbfca51 to 119c5cf Compare October 16, 2025 14:44
@fmoletta fmoletta marked this pull request as ready for review October 16, 2025 15:44
@fmoletta fmoletta requested a review from a team as a code owner October 16, 2025 15:44
@github-actions github-actions bot added the L1 Ethereum client label Oct 16, 2025
/// Block headers downloaded during fullsync column family: [`u8;_`] => [`Vec<u8>`]
/// - [`u8;_`] = `block_number.to_le_bytes()`
/// - [`Vec<u8>`] = `BlockHeaderRLP::from(block.header.clone()).bytes().clone()`
const CF_FULLSYNC_HEADERS: &str = "fullsync_headers";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be merged with the main headers table. I opened #4903 to check that.

Copy link
Collaborator

@mpaulucci mpaulucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@MegaRedHand MegaRedHand moved this to In Review in ethrex_l1 Oct 16, 2025
Comment on lines +472 to +473
if !block_headers.is_empty()
&& are_block_headers_chained(&block_headers, &order)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a change in behavior from later versions we noticed. Before, empty responses were never forwarded to the user, but somewhere down the line it changed, producing errors that were previously unreachable.

Comment on lines +357 to +358
let first_header = block_headers.first().ok_or(SyncError::NoBlocks)?;
let last_header = block_headers.last().ok_or(SyncError::NoBlocks)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These errors are actually unreachable, since request_block_headers_from_hash never returns an empty Vec. I think we should change, in another PR, request_block_headers_from_hash to return a plain Vec instead of an Option<Vec<>>, and interpret an empty Vec the same as the current None.

Copy link
Contributor

@edg-l edg-l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a nitpick comment, it can be checked in the future

// Stores current Snap State
snap_state: SnapState,
// Stores fetched headers during a fullsync
fullsync_headers: HashMap<BlockNumber, BlockHeader>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is expected to have less than < 10000~ it might be better to use a btree too here

@mpaulucci mpaulucci added this pull request to the merge queue Oct 17, 2025
Merged via the queue into main with commit 889ffd0 Oct 17, 2025
32 checks passed
@mpaulucci mpaulucci deleted the full-sync-download-headers-first branch October 17, 2025 16:39
@github-project-automation github-project-automation bot moved this from In Review to Done in ethrex_l1 Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

L1(FullSync): syncer stuck on side chain during full sync Change full-sync to download headers from latest to genesis

4 participants